package io.github.uicdb.badskill;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

public abstract class SkillOptionDataProvider implements DataProvider {
    protected final DataGenerator generator;
    protected final PackOutput.PathProvider skillProvider;
    protected final PackOutput packOutput;
    protected final String modid;
    protected final List<SkillOption> skillOptions = new ArrayList<>();

    public SkillOptionDataProvider(DataGenerator generator, String modid, boolean outToTest) {
        this.generator = generator;
        this.modid = modid;
        this.packOutput = outToTest ? getPackOutputTest(generator.getPackOutput()) : generator.getPackOutput();
        skillProvider = packOutput.createPathProvider(PackOutput.Target.DATA_PACK, "skill_option");
    }

    public CompletableFuture<?> save(SkillOption option, CachedOutput output) {
        return DataProvider.saveStable(output, option.toJson(), skillProvider.json(option.id));
    }

    protected PackOutput getPackOutputTest(PackOutput from) {
        return new PackOutput(from.getOutputFolder().getParent().getParent().resolve("test").resolve("resources"));
    }

    @Override
    public CompletableFuture<?> run(@NotNull CachedOutput cachedOutput) {
        generate(cachedOutput);
        return CompletableFuture.allOf(skillOptions.stream().map(o -> save(o, cachedOutput)).toList().toArray(new CompletableFuture[0]));
    }

    protected abstract void generate(CachedOutput output);

    public Builder add(String id) {
        return new Builder(this, modid, id);
    }

    @Override
    public String getName() {
        return getClass().getSimpleName();
    }

    @RequiredArgsConstructor
    public final static class Builder {
        private int exp;
        private final SkillOptionDataProvider provider;
        private final String namespace;
        private final String path;
        private final List<SkillOption.ResConsumerData> consumerData = new ArrayList<>();
        private final List<ItemStack> itemStacks = new ArrayList<>();
        private final Multimap<Attribute, AttributeModifier> modifiers = HashMultimap.create();

        public Builder exp(int exp) {
            this.exp = exp;
            return this;
        }

        public Builder data(ResourceLocation name, String key, double data) {
            this.consumerData.add(new SkillOption.ResConsumerData(name, key, data));
            return this;
        }

        public Builder data(DataType name, String key, double data) {
            return this.data(name.getKey(), key, data);
        }

        public Builder score(String key, double data) {
            return this.data(DataType.SCORE, key, data);
        }

        public Builder only(String key) {
            return this.data(DataType.ONLY, key, 0);
        }

        public Builder item(ItemStack... stacks) {
            this.itemStacks.addAll(Arrays.asList(stacks));
            return this;
        }

        public Builder item(Item... stacks) {
            this.itemStacks.addAll(Arrays.stream(stacks).map(Item::getDefaultInstance).toList());
            return this;
        }

        @SafeVarargs
        public final Builder item(Supplier<? extends Item>... stacks) {
            this.itemStacks.addAll(Arrays.stream(stacks).map(s -> s.get().getDefaultInstance()).toList());
            return this;
        }

        public Builder modifier(Attribute attribute, String name, double amount, AttributeModifier.Operation operation) {
            modifiers.put(attribute, new AttributeModifier(UUID.nameUUIDFromBytes((name).getBytes(StandardCharsets.UTF_8)), name, amount, operation));
            return this;
        }

        public Builder modifier(Supplier<? extends Attribute> attribute, String name, double amount, AttributeModifier.Operation operation) {
            modifiers.put(attribute.get(), new AttributeModifier(UUID.nameUUIDFromBytes((name).getBytes(StandardCharsets.UTF_8)), name, amount, operation));
            return this;
        }

        public Builder modifier(Attribute attribute, double amount, AttributeModifier.Operation operation) {
            return this.modifier(attribute, namespace + ":" + path, amount, operation);
        }

        public Builder modifier(Supplier<? extends Attribute> attribute, double amount, AttributeModifier.Operation operation) {
            return this.modifier(attribute.get(), namespace + ":" + path, amount, operation);
        }

        public SkillOption build() {
            ResourceLocation resourceLocation = new ResourceLocation(namespace, path);
            SkillOption option = new SkillOption.SkillOptionBuilder().exp(exp).consumerData(consumerData).id(resourceLocation).itemStacks(itemStacks).modifiers(modifiers).build();
            provider.skillOptions.add(option);
            return option;
        }
    }

    public enum DataType {
        EMPTY(new ResourceLocation(BadSkill.MODID, "empty")), SCORE(new ResourceLocation(BadSkill.MODID, "score")), ONLY(new ResourceLocation(BadSkill.MODID, "only"));
        @Getter
        private final ResourceLocation key;

        DataType(ResourceLocation key) {
            this.key = key;
        }
    }
}
